// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstring>
#include <deque>
#include <functional>
#include <iomanip>
#include <list>
#include <numeric>
#include <queue>
#include <string>
#include <utility>
#include <vector>
#include <memory>

#include "coin2/base/log.h"
#include "coin2/strategy/util/moving_window_top_percentile.h"

namespace xguo {

// Data types
struct Bbo {
  int64_t timestamp;
  uint64_t product_id;
  double ask0;
  double bid0;
  double midp;
  int64_t seqno;

  bool operator<(const Bbo& rhs) const { return midp < rhs.midp; }
  double value() const { return midp; }

  void CopyAndIncSeqNo(const Bbo& bbo) {
    timestamp = bbo.timestamp;
    product_id = bbo.product_id;
    ask0 = bbo.ask0;
    bid0 = bbo.bid0;
    midp = bbo.midp;
    seqno++;
  }

  Bbo() {
    // zero set all bits;
    std::memset(this, 0, sizeof(*this));
  }
};

struct Fill {
  int64_t timestamp;
  uint64_t product_id;
  double fill_price;
  double fill_qty;  // Can be negative
  double fee;

  Fill() {
    // zero set all bits;
    std::memset(this, 0, sizeof(*this));
  }

  bool operator<(const Fill& fill) const { return fill_price < fill.fill_price; }
};

struct OrderData {
  enum ExecutionType { UNKNOWN_EXECUTION_TYPE = 0, PASSIVE, AGGRESSIVE_BUY, AGGRESSIVE_SELL };

  bool success;
  ExecutionType type;

  double pass_sell_price;
  double pass_buy_price;
  double pass_sell_qty;
  double pass_buy_qty;

  double agg_sell_price;
  double agg_buy_price;
  double agg_sell_qty;
  double agg_buy_qty;
  char tag[256];

  // for debug
  double buy_price[16];
  double sell_price[16];
  double trade_edge;
  double ref_buy_rate;
  double ref_sell_rate;

  double ask0;
  double bid0;

  mutable double bp1;
  mutable double bp2;

  OrderData() {
    // zero set all bits;
    Clear();
  }

  void set_tag(const std::string& s) {
    std::memset(tag, 0, sizeof(tag));
    std::strncpy(tag, s.data(), s.size() + 1);
  }

  void Clear() {
    // zero set all bits;
    std::memset(this, 0, sizeof(*this));
  }

  void CheckPrice() const {
    bp1 = 1;
    bp2 = 1;
    if (type == ExecutionType::PASSIVE) {
      bp1 = std::log(pass_sell_price / bid0) * 10000;
      if (pass_buy_price > 0) {
        bp2 = std::log(ask0 / pass_buy_price) * 10000;
      }
      // Cannot be too faraway;
      if (bp1 > 100 || bp2 > 100) {
        CHECK(false) << DebugString();
      }
    } else if (type == ExecutionType::AGGRESSIVE_BUY) {
      bp1 = std::log(ask0 / agg_buy_price) * 10000;
      if (bp1 > 0) {
        CHECK(false) << DebugString();
      }
    } else if (type == ExecutionType::AGGRESSIVE_SELL) {
      bp1 = std::log(agg_sell_price / bid0) * 10000;
      if (bp1 > 0) {
        CHECK(false) << DebugString();
      }
    }
    LOG(INFO) << DebugString();
  }

  static const char* EnumToString(int n) {
    switch (n) {
      case UNKNOWN_EXECUTION_TYPE:
        return "UNKNOWN_EXECUTION_TYPE";
      case PASSIVE:
        return "PASSIVE";
      case AGGRESSIVE_BUY:
        return "AGGRESSIVE_BUY";
      case AGGRESSIVE_SELL:
        return "AGGRESSIVE_SELL";
      default:
        return "UNKNOWN";
    }
    return "UNKNOWN";
  }

  std::string DebugString() const {
    std::stringstream ss;
    ss << std::setprecision(5) << std::fixed;
    ss << "success:" << success << "\n";
    ss << "type: " << EnumToString(type) << "\n";

    ss << "pass_sell_price: " << pass_sell_price << "\n";
    ss << "pass_sell_qty: " << pass_sell_qty << "\n";
    ss << "pass_buy_price: " << pass_buy_price << "\n";
    ss << "pass_buy_qty: " << pass_buy_qty << "\n";

    ss << "agg_sell_price: " << agg_sell_price << "\n";
    ss << "agg_sell_qty: " << agg_sell_qty << "\n";
    ss << "agg_buy_price: " << agg_buy_price << "\n";
    ss << "agg_buy_qty: " << agg_buy_qty << "\n";

    ss << "buy_prices: [";
    for (auto p : buy_price) {
      ss << p << ", ";
    }
    ss << "]\n";

    ss << "sell_prices: [";
    for (auto p : sell_price) {
      ss << p << ", ";
    }
    ss << "]\n";

    ss << "trade_edge: " << trade_edge << "\n";
    ss << "ref_buy_rate: " << ref_buy_rate << "\n";
    ss << "ref_sell_rate: " << ref_sell_rate << "\n";
    ss << "ask0: " << ask0 << "\n";
    ss << "bid0: " << bid0 << "\n";
    ss << "bp1: " << bp1 << "\n";
    ss << "bp2: " << bp2 << "\n";

    return ss.str();
  }
};

// Utils
inline int64_t GetCurrentTimestamp() {
  using std::chrono::duration_cast;
  using std::chrono::nanoseconds;
  using std::chrono::system_clock;
  const auto now = system_clock::now().time_since_epoch();
  return duration_cast<nanoseconds>(now).count();
}

inline double CalcVariance(const std::vector<double>& vec) {
  const size_t sz = vec.size();
  if (sz < 2) {
    return 0.0;
  }

  auto func = [](std::pair<double, double> accumulator, double val) {
    return std::make_pair(accumulator.first + (val * val), accumulator.second + val);
  };

  auto p = std::accumulate(vec.begin(), vec.end(), std::make_pair(0.0, 0.0), func);
  double m = p.second / sz;
  return p.first / sz - m * m;
}

inline double CalcReturn(double open, double close) { return std::log(close / open); }

template <typename D>
class MovingWindowData {
 public:
  using MovingWindowPercentile = coin2::strategy::util::MovingWindowPercentile;

 public:
  explicit MovingWindowData(int64_t window_size) : window_size_(window_size) {
    low_ = std::make_unique<MovingWindowPercentile>(window_size, 0.01);
    high_ = std::make_unique<MovingWindowPercentile>(window_size, 0.99);
    median_ = std::make_unique<MovingWindowPercentile>(window_size, 0.50);
  }

  void Update(const D& data) {
    while (!queue_.empty() && (data.timestamp - queue_.front().timestamp > window_size_)) {
      queue_.pop_front();
    }
    queue_.push_back(data);
    low_->Add(data.timestamp, data.value());
    high_->Add(data.timestamp, data.value());
    median_->Add(data.timestamp, data.value());
  }

  auto size() const {
    // get size
    return queue_.size();
  }

  bool Sort() {
    if (queue_.size() < 50) {
      return false;
    }
    sorted_.clear();
    for (auto& elem : queue_) {
      sorted_.push_back(elem);
    }
    std::sort(sorted_.begin(), sorted_.end());
    return true;
  }

  auto GetLow() {
    double v;
    CHECK(low_->Get(&v));
    return v;
  }

  auto GetHigh() {
    double v;
    CHECK(high_->Get(&v));
    return v;
  }

  auto GetMedian() {
    double v;
    CHECK(median_->Get(&v));
    return v;
  }

 private:
  int64_t window_size_;
  std::deque<D> queue_;
  std::vector<D> sorted_;

  std::unique_ptr<MovingWindowPercentile> low_;
  std::unique_ptr<MovingWindowPercentile> high_;
  std::unique_ptr<MovingWindowPercentile> median_;
};

class OhlcCalculator {
 public:
  struct OhlcEntry {
    int64_t timestamp;
    double open;
    double high;
    double low;
    double close;
    double rtn;
  };

  explicit OhlcCalculator(int64_t window_size) : window_size_(window_size) {}
  void UpdateByBbo(const Bbo& bbo);

  void Sort() {
    sorted_.clear();
    for (const auto& elem : ohlc_) {
      sorted_.push_back(elem.rtn);
    }
    std::sort(sorted_.begin(), sorted_.end());
  }

  double volatility() const {
    constexpr int64_t T = 1'000'000'000LL;
    double v = CalcVariance(sorted_);
    return std::sqrt(v / (window_size_ / T));
  }

  const auto& IterOhlc() const { return ohlc_; }

  double last_volatility() const {
    constexpr int64_t T = 1'000'000'000LL;
    if (ohlc_.empty()) {
      return 0.0;
    }

    double W = std::sqrt(window_size_ / T);
    if (ohlc_.size() == 1) {
      return std::abs(ohlc_.back().rtn) / W;
    }

    double rtn[2] = {0, 0};
    auto it = ohlc_.crbegin();
    rtn[0] = std::abs(it->rtn);
    it++;
    rtn[1] = std::abs(it->rtn);
    return std::max(rtn[0], rtn[1]) / W;
  }

  auto size() const { return ohlc_.size(); }

 private:
  int64_t timestamp_;
  int64_t window_index_{0};
  int64_t window_size_{0};
  std::vector<Bbo> queue_;
  std::list<OhlcEntry> ohlc_;
  std::vector<double> sorted_;
};

}  // namespace xguo
